﻿// ***********************************************************************************************************************************************************
// * Copyright (c) 2008 Unit Software                                                                                                                        *
// *                                                                                                                                                         *
// * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),      *
// * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,      *
// * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:              *
// *                                                                                                                                                         *
// * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.                          *
// *                                                                                                                                                         *
// * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,     *
// * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER      *
// * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS    *
// * IN THE SOFTWARE.                                                                                                                                        *
// ***********************************************************************************************************************************************************

using System;
using System.Linq;
using NUnit.Framework;
using UnitSoftware.LINQ2SQLExtensions;

namespace LINQ2SQLExtentionsUnitTestSuite
{
    [TestFixture]
    public class ProblemDemonstration
    {
        //When working with LINQ2SQL entity classes I may want to perform a number of task (property changes, new entities etc) and then simply discard them.
        //For example, I may open an invoice and create several invoice items, but not save my changes.  The process of creating the items
        //and setting the invoice value has now added the items to the change list so the next time DataContext.SubmitChanges() is called
        //the items are persisted.  The LINQ2SQL API dictates that in this scenario I use a new DataContext to perform the tasks
        //and then simply discard the DataContext. This means I have to do a lot more database reads and it also means that any other Invoice objects in the system
        //that represent the entity I have changed (presuming I now save my changes) will be unaware of the change until they are next refreshed. I want a different model; 
        //I want one Invoice object and one DataContext and all changes are executed through this single context.  This reduces the number of objects I have and 
        //enables me build a much richer set of entity clases, with events and so on.

        //This test demonstrates the problem
        [Test]
        public void CommitChangesSolution()
        {
            var masterDataContext = DataContextFactory.INSTANCE.GetNewDataContext();
            masterDataContext.ClearAllData();

            var invoice = new Invoice {Raised = DateTime.Now};

            masterDataContext.Invoices.InsertOnSubmit(invoice);

            masterDataContext.SubmitChanges();

            var transientDataContext = DataContextFactory.INSTANCE.GetNewDataContext();
            var transientDataContextWrapper = new TransientDataContextWrapper(transientDataContext);

            var invoiceItem = new InvoiceItem();
            var invoiceItem2 = new InvoiceItem();

            transientDataContextWrapper.RegisterNew(invoiceItem);
            transientDataContextWrapper.RegisterNew(invoiceItem2);

            //Retrieve the target invoice on the transient context
            var transientInvoice = (Invoice) invoice.GetOnContext(transientDataContext);

            //Associate the invoice with items.
            invoiceItem.Invoice = invoiceItem2.Invoice = transientInvoice;

            //Set some values
            invoiceItem.Value = (invoiceItem2.Value = 100)*2;

            //Now commit change to the master context
            transientDataContextWrapper.Execute(masterDataContext);

            //SubmitChanges is called on the master...
            masterDataContext.SubmitChanges();

            Assert.IsTrue(masterDataContext.InvoiceItems.Count() == 2);
        }

        [Test]
        public void DiscardChangesProblem()
        {
            var dataContext = DataContextFactory.INSTANCE.GetNewDataContext();

            dataContext.ClearAllData();

            var invoice = new Invoice {Raised = DateTime.Now};

            dataContext.Invoices.InsertOnSubmit(invoice);

            dataContext.SubmitChanges();

            //I open the invoice and create two new items
            var invoiceItem = new InvoiceItem {Invoice = invoice, Value = 100};
            var invoiceItem2 = new InvoiceItem {Invoice = invoice, Value = 200};

            //but I now close the invoice editor without saving changes.  However, the new items are in the DataContext change set and will be created
            //at the next submit changes unless I explicitly remove them.  To do that I would need a log of what I have done so that I could undo it.  Nasty.
            //Also, the changes I have made may have triggered events on the invoice item and I may not want that to happen until I save changes.

            dataContext.SubmitChanges();

            Assert.IsTrue(dataContext.InvoiceItems.Count() == 2);
        }

        //The solution is a Master/Transient DataContext model. Perform all changes on the TransientDataContext and then replay the changes
        //on the MasterDataContext if I decide to commit my changes.  This way I can commit all changes or simply discard with no effect on the master context/entities.
        //Changes will only occur to the master entities when I commit
        [Test]
        public void DiscardChangesSolution()
        {
            var masterDataContext = DataContextFactory.INSTANCE.GetNewDataContext();

            masterDataContext.ClearAllData();

            var invoice = new Invoice {Raised = DateTime.Now};

            masterDataContext.Invoices.InsertOnSubmit(invoice);

            masterDataContext.SubmitChanges();


            var transientDataContext = DataContextFactory.INSTANCE.GetNewDataContext();
            var transientDataContextWrapper = new TransientDataContextWrapper(transientDataContext);

            var invoiceItem = new InvoiceItem();
            var invoiceItem2 = new InvoiceItem();

            //I have to register the new objects.  I have to register the objects before setting any properties as the wrapper listens for property changes so that it can reply
            //them.  This is a problem, I need to refactor so that when new objects are registered the wrapper stores the propery values.
            transientDataContextWrapper.RegisterNew(invoiceItem);
            transientDataContextWrapper.RegisterNew(invoiceItem2);

            //retrieve the target invoice on the transient context
            var transientInvoice = (Invoice) invoice.GetOnContext(transientDataContext);

            //Associate the invoice with items.
            invoiceItem.Invoice = invoiceItem2.Invoice = transientInvoice;

            //Set some values
            invoiceItem.Value = (invoiceItem2.Value = 100)*2;

            //Now do nothing because I do not want save changes

            //SubmitChanges is called on the master...
            masterDataContext.SubmitChanges();

            //..and the invoiceitems are not persisted because they never went anywhere near the master context.
            Assert.IsTrue(masterDataContext.InvoiceItems.Count() == 0);
        }
    }
}